/*
 * Copyright 2007-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.paoding.rose;

import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.paoding.rose.impl.util.SpringUtils;
import net.paoding.rose.web.ControllerErrorHandler;
import net.paoding.rose.web.ControllerInterceptor;
import net.paoding.rose.web.Invocation;
import net.paoding.rose.web.impl.module.Module;
import net.paoding.rose.web.impl.thread.ActionInvocation;
import net.paoding.rose.web.impl.thread.ControllerInvocation;
import net.paoding.rose.web.impl.thread.ModuleInvocation;
import net.paoding.rose.web.impl.thread.ModuleLocator;
import net.paoding.rose.web.impl.thread.WebInvocation;
import net.paoding.rose.web.instruction.Forward;
import net.paoding.rose.web.instruction.HttpErrorInstruction;
import net.paoding.rose.web.instruction.Instruction;
import net.paoding.rose.web.instruction.Redirect;
import net.paoding.rose.web.instruction.Text;
import net.paoding.rose.web.instruction.View;
import net.paoding.rose.web.var.FlashImpl;
import net.paoding.rose.web.var.PrivateVar;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.ClassUtils;
import org.springframework.web.util.WebUtils;

/**
 * {@link RoseEngine}从{@link RoseFilter}接收web请求，并按照Rose规则进行处理。
 * <p>
 * 
 * {@link RoseEngine}会判断该web请求是否是本{@link RoseEngine}应该处理的，如果是进行后续的委派，如果不是则只直接返回。
 * <p>
 * 
 * @author qieqie.wang@paoding.net
 * 
 */
public class RoseEngine implements WebInvocation {

	private Log logger = LogFactory.getLog(getClass());

	protected ModuleLocator moduleLocator;
	protected WebInvocation userProfile;

	public RoseEngine() {
	}

	public RoseEngine(List<Module> modules) {
		setModules(modules);
	}

	public void setModules(List<Module> modules) {
		moduleLocator = new ModuleLocator(modules);
	}

	public void setUserProfile(WebInvocation userProfile) {
		this.userProfile = userProfile;
	}

	@Override
	public Object invoke(HttpServletRequest request, HttpServletResponse response)
			throws ServletException {
		ActionInvocation actionInvocation = null;
		Object instruction = null;
		try {
			// 
			ModuleInvocation moduleInvocation = null;
			try {
				moduleInvocation = moduleLocator.locate(request, response);
				// 当没有对应的module时，表示该请求不是一个rose请求
				if (moduleInvocation == null) {
					return RoseConstants.CONTINUE;
				}
				instruction = moduleInvocation.invoke(request, response);
				if (RoseConstants.CONTINUE.equals(instruction)) {
					return RoseConstants.CONTINUE;
				}
				actionInvocation = PrivateVar.invocationVariables().getActionInvocation();
			} catch (Throwable e) {
				if (moduleInvocation == null) {
					if (e instanceof Exception) {
						throw (Exception) e;
					} else {
						throw (Error) e;
					}
				}
				while (e instanceof InvocationTargetException) {
					Throwable cause = ((InvocationTargetException) e).getCause();
					if (cause == null) {
						break;
					}
					e = cause;
				}
				Module module = moduleInvocation.getModule();
				ControllerErrorHandler errorHandler = module.getErrorHandler();
				if (errorHandler != null) {
					if (logger.isDebugEnabled()) {
						logger.debug("exception happended, ControllerErrorHandler in module '"
								+ module.getPath() + "' will handler the exception: "
							+ e.getClass().getName() + ":" + e.getMessage());
					}
				} else {
					if (e instanceof Exception) {
						throw (Exception) e;
					} else {
						throw (Error) e;
					}
				}
				instruction = errorHandler.onError(e);
				if (instruction == null) {
					instruction = "error";
				}
			}
			if (actionInvocation != null && userProfile != null) {
				userProfile.invoke(request, response);
			}
			int count = 0;
			while (!(instruction instanceof Instruction)) {
				if (count++ > 50) {
					throw new StackOverflowError("Unable to parse the instruction to an"
							+ " Instruction object less than " + count
							+ " times. Is the instruction" + " that returned by your controller"
							+ " action is right?");
				}
				if (instruction == null
						&& actionInvocation.getMethod().getReturnType() != void.class) {
					break;
				} else if (instruction == null || instruction.equals(">")
						|| instruction.equals('>')) {
					ControllerInvocation controllerInvocation = PrivateVar.invocationVariables()
							.getControllerInvocation();
					String viewPrefix = controllerInvocation.getViewPrefix();
					instruction = View.name(viewPrefix
							+ PrivateVar.invocationVariables().getActionInvocation().getMethod()
									.getName());
				} else {
					if (instruction.getClass() != String.class
							&& !ClassUtils.isPrimitiveOrWrapper(instruction.getClass())) {
						SpringUtils.autowire(instruction, moduleInvocation.getModule().getPath());
					}
					instruction = parseInstruction(instruction);
				}
			}
			// 写flash消息(被include的请求没有也不会有功能)
			if (!WebUtils.isIncludeRequest(request)) {
				FlashImpl flash = (FlashImpl) Rc.flash();
				flash.writeNewMessages(response);
			}
			if (instruction != null) {
				// 获取Spring中定义的对象...
				SpringUtils.autowire(instruction, moduleInvocation.getModule().getPath());
				// 执行render
				((Instruction) instruction).render(request, response);
			}
			if (actionInvocation != null) {
				triggerAfterCompletion(request, response);
			}
		} catch (Throwable e) {
			actionInvocation = PrivateVar.invocationVariables().getActionInvocation();
			if (actionInvocation != null) {
				// Apply afterCompletion methods of registered interceptors.
				triggerAfterCompletion(request, response, e);
				String msg = request.getRequestURL()
					.append(" --> ").append(actionInvocation).toString();
				logger.error("error happended for request:" + msg);
				throw new ServletException(msg, e);
			} else {
				throw new ServletException(request.getRequestURL().toString(), e);
			}
		}
		return instruction;
	}

	private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response) {
		this.triggerAfterCompletion(request, response, null);
	}

	private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
			Throwable ex) {
		// Apply afterCompletion methods of registered interceptors.
		ActionInvocation actionInvocation = PrivateVar.invocationVariables().getActionInvocation();
		if (actionInvocation == null) {
			return;
		}
		Invocation invocation = PrivateVar.invocationVariables().getInvocation();
		ControllerInterceptor[] interceptors = actionInvocation.getRegisteredInterceptors();
		for (int i = interceptors.length - 1; i >= 0; i--) {
			ControllerInterceptor interceptor = interceptors[i];
			if (interceptor == null) {
				continue;
			}
			try {
				interceptor.afterCompletion(invocation, ex);
			} catch (Throwable ex2) {
				logger.error("ControllerInterceptor.afterCompletion" + " threw exception", ex2);
			}
		}
		//
		// Restore the original attribute snapshot, in case of an include.
		Map<String, Object> attributesSnapshot = PrivateVar.invocationVariables()
				.getAttributesSnapshot();
		if (attributesSnapshot != null) {
			PrivateVar.invocationVariables().setAttributesSnapshot(null);
			restoreAttributesAfterInclude(request, attributesSnapshot);
		}
	}

	/**
	 * Restore the request attributes after an include.
	 * 
	 * @param request
	 *            current HTTP request
	 * @param attributesSnapshot
	 *            the snapshot of the request attributes before the include
	 */
	private void restoreAttributesAfterInclude(HttpServletRequest request,
			Map<String, Object> attributesSnapshot) {
		logger.debug("Restoring snapshot of request attributes after include");

		// Need to copy into separate Collection here, to avoid side effects
		// on the Enumeration when removing attributes.
		Set<String> attrsToCheck = new HashSet<String>();
		Enumeration<?> attrNames = request.getAttributeNames();
		while (attrNames.hasMoreElements()) {
			String attrName = (String) attrNames.nextElement();
			attrsToCheck.add(attrName);
		}

		// Iterate over the attributes to check, restoring the original value
		// or removing the attribute, respectively, if appropriate.
		for (Iterator<String> it = attrsToCheck.iterator(); it.hasNext();) {
			String attrName = it.next();
			Object attrValue = attributesSnapshot.get(attrName);
			if (attrValue != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Restoring original value of attribute [" + attrName
							+ "] after include");
				}
				request.setAttribute(attrName, attrValue);
			} else {
				if (logger.isDebugEnabled()) {
					logger.debug("Removing attribute [" + attrName + "] after include");
				}
				request.removeAttribute(attrName);
			}
		}
	}

	private class StringInstruction {
		Boolean forward;
		String innerInstruction;

		public StringInstruction(Boolean forward, String innerInstruction) {
			super();
			this.forward = forward;
			this.innerInstruction = innerInstruction;
		}

		public boolean isReirect() {
			return forward != null && !forward.booleanValue();
		}

		public boolean isForward() {
			return forward != null && forward.booleanValue();
		}

		@Override
		public String toString() {
			return forward == null ? "" : (forward ? "f:" : "r:") + innerInstruction;
		}
	}

	protected Object parseInstruction(Object instruction) {
		if (logger.isDebugEnabled()) {
			logger.debug("parset instruction:" + instruction.getClass().getName() + ": '"
					+ instruction + "'");
		}
		if (ClassUtils.isPrimitiveOrWrapper(instruction.getClass())) {
			return Text.plain(instruction);
		} else if (instruction instanceof CharSequence) {
			String str = instruction.toString();
			if (str.length() == 0) {
				return Text.plain("");
			}
			if (str.charAt(0) == '@') {
				return Text.html(str.substring(1)); // 使用html而非plain，如有需要可以通过@
				// HttpFeatures进行设置更改
			}
			if (str.charAt(0) == '/') {
				return View.name(str);
			}
			if (str.startsWith("f:") || str.startsWith("forward:")) {
				return new StringInstruction(true, str.substring(str.indexOf(':') + 1));
			}
			if (str.startsWith("r:") || str.startsWith("redirect:")) {
				StringInstruction si = new StringInstruction(false, str
						.substring(str.indexOf(':') + 1));
				if (si.innerInstruction.startsWith("http://")
						|| si.innerInstruction.startsWith("https://")) {
					return Redirect.location(si.innerInstruction);
				}
				return si;
			}
			if (str.startsWith("s:") || str.startsWith("status:")) {
				int index = str.indexOf(':') + 1;
				int other = str.indexOf(';', index);
				if (other == -1) {
					Rc.response().setStatus(Integer.parseInt(str.substring(index)));
					return null;
				} else {
					Rc.response().setStatus(Integer.parseInt(str.substring(index, other)));
					for (int i = other; i < str.length(); i++) {
						if (str.charAt(i) != ' ') {
							str = str.substring(i + 1);
							break;
						}
					}
				}
			} else if (str.startsWith("e:") || str.startsWith("error:")) {
				int msgIndex = str.indexOf(';');
				if (msgIndex == -1) {
					return new HttpErrorInstruction(Integer.parseInt(str
							.substring(str.indexOf(':') + 1)));
				} else {
					return new HttpErrorInstruction(Integer.parseInt(str.substring(
							str.indexOf(':') + 1, msgIndex)), str.substring(msgIndex + 1));
				}
			}
			return new StringInstruction(null, str);
		} else if (instruction.getClass() == StringInstruction.class) {
			StringInstruction fr = (StringInstruction) instruction;
			String str = fr.innerInstruction;
			int queryIndex = str.indexOf('?');
			for (int i = queryIndex == -1 ? str.length() - 1 : queryIndex - 1; i >= 0; i--) {
				if (str.charAt(i) != ':') {
					continue;
				}
				if (i > 0 && str.charAt(i - 1) == '\\') {
					str = str.substring(0, i - 1) + str.substring(i);
					continue;
				}
				int cmdEnd = i;
				int cmdBeforeBegin = i - 1;
				while (cmdBeforeBegin >= 0 && str.charAt(cmdBeforeBegin) != ':') {
					cmdBeforeBegin--;
				}
				String prefix = str.subSequence(cmdBeforeBegin + 1, cmdEnd).toString();
				String body = str.subSequence(i + 1, str.length()).toString();
				if ("a".equals(prefix) || "action".equals(prefix)) {
					if (fr.isReirect()) {
						return Redirect.action(body);
					} else {
						return Forward.action(body);
					}
				}
				if ("c".equals(prefix) || "controller".equals(prefix)) {
					if (fr.isReirect()) {
						return Redirect.controller(body);
					} else {
						return Forward.controller(body);
					}
				}
				if ("m".equals(prefix) || "module".equals(prefix)) {
					if (fr.isReirect()) {
						return Redirect.module(body);
					} else {
						return Forward.module(body);
					}
				}
				logger.warn("skip the prefix '" + prefix + ":' of " + str);
				if (fr.isReirect()) {
					return Redirect.location(str);
				} else if (fr.isForward()) {
					return Forward.path(str);
				} else {
					return View.name(str);
				}
			}
			if (fr.isReirect()) {
				return Redirect.location(str);
			} else if (fr.isForward()) {
				return Forward.path(str);
			}
			return View.name(str);
		} else {
			return Text.plain(instruction.toString());
		}
	}

}
